import tkinter as tk
from tkinter import ttk, messagebox, filedialog
import requests
import json
import threading
import pyaudio
from pydub import AudioSegment
from pydub.playback import play
import tempfile
import os
from OpusPlayer import play_opus_stream

HTTP_PATH = "http://192.168.2.4:8000"
DEFAULT_VOICE = "ying"
DEFAULT_INPUT = "你不要让我做饭啦，我什么都能办到，但是真的不会做饭。"

# 音频格式设置
FORMAT_PCM = 0
FORMAT_OPUS = 1

class VoiceSynthesizerApp:
    def __init__(self, root):
        self.root = root
        self.root.title("语音合成工具")
        self.root.geometry("700x750")  # 增加高度以适应新控件
        self.current_voice = DEFAULT_VOICE
        self.audio = pyaudio.PyAudio()
        self.voice_map = {}  # 存储title到name的映射
        self.voices_list = []  # 存储音色数据
        self.stream_format = FORMAT_PCM  # 默认使用PCM格式
        
        # 主框架
        main_frame = ttk.Frame(root)
        main_frame.pack(fill='both', expand=True, padx=20, pady=20)
        
        # 音色管理区域
        voice_frame = ttk.LabelFrame(main_frame, text="音色管理")
        voice_frame.pack(fill='x', pady=(0, 15))
        
        # 音色选择和设置
        ttk.Label(voice_frame, text="当前音色:").grid(row=0, column=0, padx=5, pady=5, sticky='w')
        self.current_voice_label = ttk.Label(voice_frame, text=self.current_voice, width=15)
        self.current_voice_label.grid(row=0, column=1, padx=5, pady=5, sticky='w')
        
        ttk.Label(voice_frame, text="选择音色:").grid(row=0, column=2, padx=5, pady=5, sticky='w')
        self.voice_combobox = ttk.Combobox(voice_frame, width=15, state='readonly')
        self.voice_combobox.grid(row=0, column=3, padx=5, pady=5)
        self.voice_combobox.set(self.current_voice)
        self.voice_combobox.bind("<<ComboboxSelected>>", self.on_voice_selected)
        
        self.fetch_voices_btn = ttk.Button(voice_frame, text="获取可用音色", command=self.load_voices)
        self.fetch_voices_btn.grid(row=0, column=4, padx=5, pady=5)
        
        # 音色列表显示
        self.voices_text = tk.Text(voice_frame, height=10, width=80)
        self.voices_text.grid(row=1, column=0, columnspan=6, padx=10, pady=10)
        self.voices_text.insert('1.0', "点击'获取可用音色'按钮加载音色信息...")
        self.voices_text.config(state='disabled')
        
        # 公共输入区域
        input_frame = ttk.LabelFrame(main_frame, text="输入文本")
        input_frame.pack(fill='x', pady=(0, 15))
        
        self.common_input_text = tk.Text(input_frame, height=10)
        self.common_input_text.pack(fill='both', expand=True, padx=10, pady=5)
        self.common_input_text.insert('1.0', DEFAULT_INPUT)
        
        # 流式合成区域
        stream_frame = ttk.LabelFrame(main_frame, text="流式合成（实时播放支持）")
        stream_frame.pack(fill='x', pady=(0, 15))
        
        # 添加音频格式选择
        format_frame = ttk.Frame(stream_frame)
        format_frame.grid(row=0, column=0, columnspan=2, padx=5, pady=5, sticky='w')
        ttk.Label(format_frame, text="音频格式:").pack(side='left')
        self.format_var = tk.StringVar(value="PCM")
        self.format_combobox = ttk.Combobox(format_frame, textvariable=self.format_var, 
                                            values=["PCM", "Opus"], width=10, state="readonly")
        self.format_combobox.pack(side='left', padx=5)
        self.format_combobox.bind("<<ComboboxSelected>>", self.on_format_selected)
        
        # 输出文件设置
        file_frame = ttk.Frame(stream_frame)
        file_frame.grid(row=1, column=0, columnspan=2, padx=5, pady=5, sticky='we')
        ttk.Label(file_frame, text="输出文件名:").pack(side='left')
        self.output_file = ttk.Entry(file_frame, width=25)
        self.output_file.insert(0, "output.pcm")
        self.output_file.pack(side='left', padx=5, fill='x', expand=True)
        
        # 操作按钮
        btn_frame = ttk.Frame(stream_frame)
        btn_frame.grid(row=2, column=0, columnspan=2, pady=10)
        
        self.stream_synth_btn = ttk.Button(btn_frame, text="合成并保存", command=self.stream_synthesize)
        self.stream_synth_btn.pack(side='left', padx=5)
        
        self.play_pcm_btn = ttk.Button(btn_frame, text="播放音频文件", command=self.play_audio_file)
        self.play_pcm_btn.pack(side='left', padx=5)
        
        self.play_stream_btn = ttk.Button(btn_frame, text="直接播放音频流", command=self.direct_stream_play)
        self.play_stream_btn.pack(side='left', padx=5)
        
        # 文件合成区域
        file_frame = ttk.LabelFrame(main_frame, text="文件合成")
        file_frame.pack(fill='x')
        
        ttk.Label(file_frame, text="输出文件名:").grid(row=0, column=0, padx=5, pady=5, sticky='w')
        self.filename = ttk.Entry(file_frame, width=30)
        self.filename.insert(0, "my_audio.wav")
        self.filename.grid(row=0, column=1, padx=5, pady=5, sticky='we')
        
        ttk.Button(file_frame, text="浏览...", command=self.browse_file).grid(row=0, column=2, padx=5, pady=5)
        
        synth_btn_frame = ttk.Frame(file_frame)
        synth_btn_frame.grid(row=1, column=0, columnspan=3, pady=10)
        
        self.synth_btn = ttk.Button(synth_btn_frame, text="合成WAV文件", command=self.full_synthesize)
        self.synth_btn.pack(side='left', padx=5)
        
        self.play_wav_btn = ttk.Button(synth_btn_frame, text="播放WAV", command=self.play_wav)
        self.play_wav_btn.pack(side='left', padx=5)
        
        # 状态栏
        self.status_var = tk.StringVar()
        self.status_var.set("就绪")
        status_bar = ttk.Label(root, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W)
        status_bar.pack(side=tk.BOTTOM, fill=tk.X)
        
        # 默认加载音色
        self.load_voices()
    
    def on_voice_selected(self, event):
        """音色选择变化时更新当前音色"""
        selected_title = self.voice_combobox.get()
        if selected_title in self.voice_map:
            self.current_voice = self.voice_map[selected_title]
            self.current_voice_label.config(text=self.current_voice)
            self.set_status(f"当前音色: {selected_title}")
        else:
            self.set_status("未找到对应的音色")
    
    def on_format_selected(self, event):
        """音频格式选择变化时更新"""
        format_str = self.format_var.get()
        if format_str == "PCM":
            self.stream_format = FORMAT_PCM
            self.output_file.delete(0, 'end')
            self.output_file.insert(0, "output.pcm")
        else:  # Opus
            self.stream_format = FORMAT_OPUS
            self.output_file.delete(0, 'end')
            self.output_file.insert(0, "output.opus")
    
    def set_status(self, message):
        self.status_var.set(message)
        self.root.update_idletasks()
    
    def fetch_voices(self):
        try:
            self.set_status("正在获取音色列表...")
            self.fetch_voices_btn.config(state='disabled')
            response = requests.get(HTTP_PATH + "/voices")
            response.raise_for_status()
            data = response.json()
            
            self.voices_text.config(state='normal')
            self.voices_text.delete('1.0', 'end')
            self.voices_text.insert('1.0', json.dumps(data, indent=2, ensure_ascii=False))
            self.voices_text.config(state='disabled')
            
            # 更新音色下拉菜单和映射
            self.voice_map = {}
            voices_titles = []
            self.voices_list = data.get('voices', [])
            
            for voice in self.voices_list:
                title = voice.get('title', '')
                name = voice.get('name', '')
                if title and name:
                    self.voice_map[title] = name
                    voices_titles.append(title)
            
            if not voices_titles:
                voices_titles = [v.get('name', '') for v in self.voices_list]
                for voice in self.voices_list:
                    if 'name' in voice:
                        self.voice_map[voice['name']] = voice['name']
            
            self.voice_combobox['values'] = voices_titles
            if voices_titles:
                current_title = self.current_voice
                # 尝试找到当前音色对应的标题
                for voice in self.voices_list:
                    if voice.get('name') == self.current_voice:
                        current_title = voice.get('title', self.current_voice)
                        break
                if current_title in voices_titles:
                    self.voice_combobox.set(current_title)
                else:
                    self.voice_combobox.current(0)
                    self.current_voice = self.voice_map.get(voices_titles[0], '')
                    self.current_voice_label.config(text=self.current_voice)
            
            self.set_status(f"成功获取 {len(voices_titles)} 个音色")
        except requests.exceptions.RequestException as e:
            self.set_status(f"网络错误: {str(e)}")
            messagebox.showerror("错误", f"获取音色失败: {str(e)}")
        except Exception as e:
            self.set_status(f"错误: {str(e)}")
            messagebox.showerror("错误", f"获取音色失败: {str(e)}")
        finally:
            self.fetch_voices_btn.config(state='normal')
    
    def load_voices(self):
        threading.Thread(target=self.fetch_voices, daemon=True).start()
    
    def get_common_text(self):
        """获取公共输入框的文本内容"""
        return self.common_input_text.get("1.0", "end").strip()
    
    def stream_synthesize(self):
        text = self.get_common_text()
        if not text:
            messagebox.showwarning("警告", "请输入合成文本")
            return
            
        filename = self.output_file.get().strip()
        
        def run():
            try:
                self.set_status("正在进行流式合成...")
                self.stream_synth_btn.config(state='disabled')
                
                # 构建请求URL
                url = f"{HTTP_PATH}/stream?voice={self.current_voice}"
                if self.stream_format == FORMAT_OPUS:
                    url += "&format=opus"
                
                with requests.post(
                    url,
                    headers={"Content-Type": "text/plain"},
                    data=text.encode('utf-8'),
                    stream=True
                ) as r:
                    r.raise_for_status()
                    
                    with open(filename, 'wb') as f:
                        total_size = int(r.headers.get('content-length', 0))
                        chunk_size = 8192
                        received = 0
                        
                        for chunk in r.iter_content(chunk_size=chunk_size):
                            f.write(chunk)
                            received += len(chunk)
                            progress = received / total_size * 100 if total_size > 0 else 0
                            self.set_status(f"下载中: {min(received, total_size)}/{total_size}字节 ({progress:.1f}%)")
                
                self.set_status(f"音频文件已保存: {filename}")
                messagebox.showinfo("成功", f"音频文件已保存为: {filename}")
            except requests.exceptions.RequestException as e:
                self.set_status(f"网络错误: {str(e)}")
                messagebox.showerror("错误", f"流式合成失败: {str(e)}")
            except Exception as e:
                self.set_status(f"错误: {str(e)}")
                messagebox.showerror("错误", f"流式合成失败: {str(e)}")
            finally:
                self.stream_synth_btn.config(state='normal')
                self.set_status("就绪")
        
        threading.Thread(target=run, daemon=True).start()
    
    def play_audio_file(self):
        """播放音频文件，支持PCM和Opus格式"""
        filename = self.output_file.get().strip()
        
        def run():
            try:
                self.set_status("正在播放音频文件...")
                self.play_pcm_btn.config(state='disabled')
                
                if filename.endswith('.pcm'):
                    # PCM格式使用PyAudio播放
                    stream = self.audio.open(
                        format=pyaudio.paFloat32,
                        channels=1,
                        rate=24000,
                        output=True
                    )
                    
                    with open(filename, 'rb') as pcm_file:
                        data = pcm_file.read(512)  # 32位浮点=4字节
                        while data:
                            stream.write(data)
                            data = pcm_file.read(512)
                    
                    stream.stop_stream()
                    stream.close()
                
                elif filename.endswith('.opus'):
                    # Opus格式使用pydub播放
                    # 创建临时wav文件
                    with tempfile.NamedTemporaryFile(suffix='.wav', delete=False) as tmp:
                        temp_file = tmp.name
                    
                    try:
                        # 转换Opus到WAV
                        audio = AudioSegment.from_file(filename, format="opus")
                        audio.export(temp_file, format="wav")
                        
                        # 播放WAV文件
                        audio = AudioSegment.from_file(temp_file, format="wav")
                        play(audio)
                    finally:
                        # 清理临时文件
                        if os.path.exists(temp_file):
                            os.unlink(temp_file)
                
                else:
                    messagebox.showerror("错误", "不支持的音频格式")
                    return
                
                self.set_status("音频播放完成")
            except Exception as e:
                self.set_status(f"播放错误: {str(e)}")
                messagebox.showerror("错误", f"播放音频失败: {str(e)}")
            finally:
                self.play_pcm_btn.config(state='normal')
        
        threading.Thread(target=run, daemon=True).start()
    
    def direct_stream_play(self):
        text = self.get_common_text()
        if not text:
            messagebox.showwarning("警告", "请输入合成文本")
            return
        
        def run():
            try:
                self.set_status("正在实时播放音频流...")
                self.play_stream_btn.config(state='disabled')
                
                # 构建请求URL
                url = f"{HTTP_PATH}/stream?voice={self.current_voice}"
                if self.stream_format == FORMAT_OPUS:
                    url += "&format=opus"
                
                if self.stream_format == FORMAT_PCM:
                    self.play_pcm_stream(url, text)
                else:  # Opus格式
                    play_opus_stream(url, text)
                self.set_status("实时音频流播放完成")
            except requests.exceptions.RequestException as e:
                self.set_status(f"网络错误: {str(e)}")
                messagebox.showerror("错误", f"播放音频流失败: {str(e)}")
            except Exception as e:
                self.set_status(f"错误: {str(e)}")
                messagebox.showerror("错误", f"播放音频流失败: {str(e)}")
            finally:
                self.play_stream_btn.config(state='normal')
                self.set_status("就绪")
        
        threading.Thread(target=run, daemon=True).start()
    
    def browse_file(self):
        file = filedialog.asksaveasfilename(
            title="保存WAV文件",
            filetypes=[("WAV 文件", "*.wav")],
            defaultextension=".wav"
        )
        if file:
            self.filename.delete(0, 'end')
            self.filename.insert(0, file)
    
    def full_synthesize(self):
        text = self.get_common_text()
        if not text:
            messagebox.showwarning("警告", "请输入合成文本")
            return
            
        filename = self.filename.get().strip()
        
        def run():
            try:
                self.set_status("正在合成WAV文件...")
                self.synth_btn.config(state='disabled')
                
                response = requests.post(
                    f"{HTTP_PATH}/synthesize?voice={self.current_voice}&filename={self.current_voice}",
                    headers={"Content-Type": "text/plain"},
                    data=text.encode('utf-8')
                )
                response.raise_for_status()
                
                with open(filename, 'wb') as f:
                    f.write(response.content)
                
                self.set_status(f"WAV文件已保存: {filename}")
                messagebox.showinfo("成功", f"WAV文件已保存为: {filename}")
            except requests.exceptions.RequestException as e:
                self.set_status(f"网络错误: {str(e)}")
                messagebox.showerror("错误", f"合成失败: {str(e)}")
            except Exception as e:
                self.set_status(f"错误: {str(e)}")
                messagebox.showerror("错误", f"完整合成失败: {str(e)}")
            finally:
                self.synth_btn.config(state='normal')
        
        threading.Thread(target=run, daemon=True).start()
    
    def play_wav(self):
        filename = self.filename.get().strip()
        
        def run():
            try:
                self.set_status("正在播放WAV音频...")
                self.play_wav_btn.config(state='disabled')
                
                # 使用pydub播放WAV文件
                audio = AudioSegment.from_file(filename, format="wav")
                play(audio)
                
                self.set_status("WAV音频播放完成")
            except Exception as e:
                self.set_status(f"播放错误: {str(e)}")
                messagebox.showerror("错误", f"播放WAV失败: {str(e)}")
            finally:
                self.play_wav_btn.config(state='normal')
        
        threading.Thread(target=run, daemon=True).start()

    def play_pcm_stream(self, url, text):
        # PCM格式使用PyAudio实时播放
        stream = self.audio.open(
            format=pyaudio.paFloat32,
            channels=1,
            rate=24000,
            output=True
        )
        
        with requests.post(
            url,
            headers={"Content-Type": "text/plain"},
            data=text.encode('utf-8'),
            stream=True
        ) as r:
            r.raise_for_status()
            
            # 获取并播放音频数据
            for data in r.iter_content(chunk_size=512):
                if data:
                    stream.write(data)
        
        stream.stop_stream()
        stream.close()
                    
if __name__ == "__main__":
    root = tk.Tk()
    app = VoiceSynthesizerApp(root)
    root.mainloop()